using System;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;
using System.IO;
using System.Collections;
using System.Reflection;
using System.Text.RegularExpressions;
using Nini.Config;
using ICSharpCode.SharpZipLib.Zip;
using ICSharpCode.SharpZipLib.Checksums;

namespace xrcopy
{
	class Program
	{
		// Constants
		private const String srcFile = "source.txt";
		private const String dstFile = "target.txt";
		private const String jobFile = "xrcopy.rcj";
        private const String iniFile = "xrcopy.ini";
        private const String kllFile = "tskill.txt";

        private static Boolean IniBoolean(ref IConfigSource source, String config, String key, Boolean defaultValue)
        {
            if (source.Configs[config] == null)
                source.Configs.Add(config);

            if (!source.Configs[config].Contains(key))
                source.Configs[config].Set(key, defaultValue);

            return source.Configs[config].GetBoolean(key, defaultValue);
        }

        private static String IniString(ref IConfigSource source, String config, String key, String defaultValue)
        {
            if (source.Configs[config] == null)
                source.Configs.Add(config);

            if (!source.Configs[config].Contains(key))
                source.Configs[config].Set(key, defaultValue);

            return source.Configs[config].GetString(key, defaultValue);
        }

        static void Main(string[] args)
		{
            DateTime startTime = DateTime.Now;
			// Just a string var for stuff
			String info = String.Empty;

			// Set the title of the window
            Console.Title = "xrcopy v" + Assembly.GetExecutingAssembly().GetName(false).Version;

			// Make sure we have everything we need!
			if (InValid())
				return;

            // Read ini Settings
            IConfigSource ini = new IniConfigSource(iniFile); ini.AutoSave = false;
            Boolean unicodeLog = IniBoolean(ref ini, "Logging", "Unicode", false);
            Boolean addTimesToLog = IniBoolean(ref ini, "Logging", "AddRunTime", true);
            Boolean zipLogFile = IniBoolean(ref ini, "Logging", "ZipLogFile", true);
            String robocopy = IniString(ref ini, "Programs", "RoboCopy", "robocopy");
            String taskkill = IniString(ref ini, "Programs", "TaskKill", "taskkill");
            Boolean useTheForce = IniBoolean(ref ini, "Programs", "UseTheForce", true);
            ini.Save();
            ini = null;

            String logType = unicodeLog ? "UNILOG" : "LOG";

			ArrayList Sources = TextlistToArray(srcFile);
			ArrayList Destinations = TextlistToArray(dstFile);
            ArrayList TasksToKill = TextlistToArray(kllFile);
            String logDate = DateTime.Now.ToString("yyyyMMdd_HHmmss");

            foreach (String task in TasksToKill)
            {
                Exec(taskkill, task);

                if (useTheForce && (taskkill == "taskkill" || taskkill.EndsWith("taskkill") || taskkill.EndsWith("taskkill.exe")))
                    Exec(taskkill, String.Format("{0} {1}", "/F", task));
            }

			foreach (String dst in Destinations)
			{
                DateTime dstStartTime = DateTime.Now;

                String logFullname = Path.Combine(dst, "Session_" + logDate + (unicodeLog ? ".unicode" : String.Empty) + ".log");
				info = String.Format("Backing files to: {0}", dst);
				Console.WriteLine(info);
				Console.WriteLine(new String('-', info.Length));

				if (!Directory.Exists(dst))
                    try
                    {
                        Directory.CreateDirectory(dst);
                    }
                    catch (Exception exception)
                    {
                        if (dst.StartsWith(@"\\"))
                        {
                            Exec("net", String.Format("use {0}", dst));
                        }
                        else
                        {
                            Console.WriteLine(exception.Message);
                            Console.WriteLine("Press any key to continue . . .");
                            Console.ReadKey();
                        }
                    }

                if (Directory.Exists(dst))
                {
                    foreach (String src in Sources)
                    {
                        if (Directory.Exists(src))
                        {
                            Console.WriteLine("* Backing directory: {0}", src);
                            string dstC = Path.Combine(dst, src.Replace(":", "_(HDD)").Replace(@"\\", @"Network\"));
                            info = string.Format("\"{0}\" \"{1}\" /JOB:xrcopy /{2}+:\"{3}\"", src, dstC, logType, logFullname);
                            Exec(robocopy, info);
                        }
                        else if (File.Exists(src))
                        {
                            FileInfo fi = new FileInfo(src);

                            String srcPath = fi.Directory.FullName;
                            String srcName = fi.Name;

                            Console.WriteLine("* Backing file: {0}", src);
                            String dstC = Path.Combine(dst, srcPath.Replace(":", "_(HDD)"));
                            info = String.Format("\"{0}\" \"{1}\" {2} /JOB:xrcopy /{3}+:\"{4}\"", srcPath, dstC, srcName, logType, logFullname);
                            Exec(robocopy, info);
                        }
                        else
                        {
                            Console.WriteLine("* file/directory not found, skipping \"{0}\"", src);
                        }
                    }

                    Console.WriteLine(String.Empty);

                    if (addTimesToLog)
                        AddTimesToLog(logFullname, dstStartTime, unicodeLog);

                    if (zipLogFile)
                    {
                        FileInfo fi = new FileInfo(logFullname);
                        CompressFile(fi.FullName, fi.Name, true);
                    }
                }
			}

            TimeSpan elapsedTime = DateTime.Now.Subtract(startTime);

            Console.WriteLine("Start Time: {0}", startTime.ToString());
            Console.WriteLine("Stop  Time: {0}", DateTime.Now.ToString());
            Console.WriteLine("Time elapsed: {0}", elapsedTime.ToString());
#if DEBUG
            Console.WriteLine("Press any key to continue . . .");
            Console.ReadKey();
#else
            Int32 TimeOut = 30;
            Console.WriteLine("Exiting in {0} seconds", TimeOut);
			DateTime killTime = DateTime.Now.AddSeconds(TimeOut);
            while (DateTime.Now < killTime)
			{
				// Do Nothing untill timeout
			}
#endif
        }

        private static void CompressFile(String fullname, String shortname, Boolean deleteOrigWhenDone)
        {
            if (!File.Exists(fullname))
                return;

            Crc32 crc = new Crc32();
            ZipOutputStream zipStream = new ZipOutputStream(File.Create(fullname + ".zip"));
            zipStream.SetLevel(9);

            using (FileStream fs = File.OpenRead(fullname))
            {
                Byte[] buffer = new Byte[fs.Length];
                fs.Read(buffer, 0, buffer.Length);

                ZipEntry entry = new ZipEntry(shortname);
                entry.DateTime = DateTime.Now;
                entry.Size = fs.Length;
                fs.Close();

                crc.Reset();
                crc.Update(buffer);

                entry.Crc = crc.Value;

                zipStream.PutNextEntry(entry);
                zipStream.Write(buffer, 0, buffer.Length);
                zipStream.Finish();
                zipStream.Close();
            }

            if (deleteOrigWhenDone)
                File.Delete(fullname);
        }

        private static void AddTimesToLog(String logFullname, DateTime dstStartTime, Boolean unicode)
        {
            StreamWriter sw = new StreamWriter(logFullname, true, (unicode ? Encoding.Unicode : Encoding.Default));
            sw.WriteLine("------------------------------------------------------------------------------");
            sw.WriteLine("Start Time: {0}", dstStartTime.ToString());
            sw.WriteLine("Stop  Time: {0}", DateTime.Now.ToString());
            sw.WriteLine("Time elapsed: {0}", DateTime.Now.Subtract(dstStartTime).ToString());
            sw.WriteLine("------------------------------------------------------------------------------");
            sw.Flush(); sw.Close(); sw.Dispose();
        }

		private static ArrayList TextlistToArray(String fileName)
		{
			ArrayList lines = new ArrayList();

            if (!File.Exists(fileName))
                return lines;

			StreamReader sr = new StreamReader(fileName);
            Regex rxSplit = new Regex(@"(\:\:|\#)");
			while (sr.Peek() != -1)
			{
				String line = sr.ReadLine().Trim();

                if (!line.StartsWith("#") && !line.StartsWith("::") && line.Length > 1)
                {
                    lines.Add(rxSplit.Split(line)[0].Trim());
                }

			}
			sr.Close(); sr.Dispose();

			return lines;
		}

        private static Boolean InValid()
		{
            Boolean retVal = false;

			if (!File.Exists(srcFile))
			{
				Console.WriteLine("Create: \"{0}\"", Path.Combine(System.Environment.CurrentDirectory, srcFile));
				Console.WriteLine(" Edit it and specify files/directories to backup (one per line)");
				Console.WriteLine(" Rows beginning with # or :: will be skipped");
				Console.WriteLine(String.Empty);

                // Create an example file!
                StreamWriter sw = new StreamWriter(srcFile);
                sw.WriteLine(@"# C:\Users :: UserData");
                sw.Flush(); sw.Close(); sw.Dispose();

				retVal = true;
			}

			if (!File.Exists(dstFile))
			{
				Console.WriteLine("Create: \"{0}\"", Path.Combine(System.Environment.CurrentDirectory, dstFile));
                Console.WriteLine(" Edit it and specify backup location(s) (one per line)");
				Console.WriteLine(" Rows beginning with # or :: will be skipped");
				Console.WriteLine(String.Empty);

                // Create an example file!
                StreamWriter sw = new StreamWriter(dstFile);
                sw.WriteLine(@"#/IM outlook.exe :: Outlook locks files I'm afraid so safer to ask it to exit");
                sw.WriteLine(@"#/F /IM outlook.exe :: Force shutdown");
                sw.WriteLine(string.Empty);
                sw.WriteLine(@"#/IM miranda32.exe :: Miranda");
                sw.WriteLine(@"#/F /IM miranda32.exe :: Miranda");
                sw.WriteLine(string.Empty);
                sw.WriteLine(@"#/IM miranda32.exe :: Miranda");
                sw.WriteLine(@"#/F /IM miranda32.exe :: Miranda");
                sw.Flush(); sw.Close(); sw.Dispose();

				retVal = true;
			}

            if (!File.Exists(kllFile))
            {
                Console.WriteLine("Create: \"{0}\"", Path.Combine(System.Environment.CurrentDirectory, kllFile));
                Console.WriteLine(" Edit it and write down arguments for taskkill if any.");
                Console.WriteLine(" Rows beginning with # or :: will be skipped");
                Console.WriteLine(String.Empty);

                // Create a default taskkill file!
                Assembly a = Assembly.GetExecutingAssembly();
                StreamReader sr = new StreamReader(a.GetManifestResourceStream("xrcopy.Resources." + kllFile));
                StreamWriter sw = new StreamWriter(kllFile, false);
                sw.Write(sr.ReadToEnd());
                sw.Flush(); sw.Close(); sw.Dispose();
                sr.Close(); sr.Dispose();
            }

            if (!File.Exists(jobFile))
			{
                Console.WriteLine("Create: \"{0}\"", Path.Combine(System.Environment.CurrentDirectory, jobFile));
                Console.WriteLine(" Edit it and specify options");
                Console.WriteLine(String.Empty);

				// Create a default job file!
                Assembly a = Assembly.GetExecutingAssembly();
                StreamReader sr = new StreamReader(a.GetManifestResourceStream("xrcopy.Resources." + jobFile));
                StreamWriter sw = new StreamWriter(jobFile, false);
                sw.Write(sr.ReadToEnd());
                sw.Flush(); sw.Close(); sw.Dispose();
                sr.Close(); sr.Dispose();
			}

            if (!File.Exists(iniFile))
            {
                Console.WriteLine("Create: \"{0}\"", Path.Combine(System.Environment.CurrentDirectory, iniFile));
                Console.WriteLine(" Edit it if you need to");
                Console.WriteLine(String.Empty);

                // Create a default ini file!
                Assembly a = Assembly.GetExecutingAssembly();
                StreamReader sr = new StreamReader(a.GetManifestResourceStream("xrcopy.Resources." + iniFile));
                StreamWriter sw = new StreamWriter(iniFile, false);
                sw.Write(sr.ReadToEnd());
                sw.Flush(); sw.Close(); sw.Dispose();
                sr.Close(); sr.Dispose();
            }

            if (retVal)
            {
                // Open containing folder
                ShellExecute(System.Environment.CurrentDirectory);
                Console.WriteLine("Press any key to exit");
                Console.ReadKey();
            }

            return retVal;
		}

        private static void ShellExecute(String item)
        {
            ProcessStartInfo psi = new ProcessStartInfo(item);
            psi.UseShellExecute = true;

            Process proc = new Process();
            proc.StartInfo = psi;
            proc.Start();
            proc.Dispose();
        }

		private static void Exec(String fileName)
		{
			Exec(fileName, String.Empty);
		}
		private static void Exec(String fileName, String arguments)
		{
            try
            {
                ProcessStartInfo psi = new ProcessStartInfo(fileName, arguments);
                psi.CreateNoWindow = false;
                psi.UseShellExecute = false;

                Process proc = new Process();
                proc.StartInfo = psi;
                proc.Start();
                proc.WaitForExit();
                proc.Dispose();
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
                Console.WriteLine("Press any key to continue . . .");
                Console.ReadKey();
            }
		}
	}
}
